﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Net;
using System.Runtime.Serialization.Json;
using System.Text;

namespace Crocodoc
{
    /// <summary>
    /// Provides access to the Crocodoc API. This is a base class that can be used
    /// standalone with full access to the other Crocodoc API clients (Document,
    /// Download, and Session), and is also used internally by the other Crocodoc API
    /// clients for generic methods including error and request.
    /// </summary>
    public class Crocodoc
    {
        /// <summary>
        /// The developer's Crocodoc API token.
        /// </summary>
        private static string _apiToken;

        /// <summary>
        /// The default base path on the server where the API lives.
        /// </summary>
        private static string _basePath = "/api/v2";

        /// <summary>
        /// The default host.
        /// </summary>
        private static string _host = "crocodoc.com";

        /// <summary>
        /// An API path relative to the base API path.
        /// </summary>
        private static string _path = "/";

        /// <summary>
        /// The default protocol (Crocodoc uses HTTPS).
        /// </summary>
        private static string _protocol = "https";

        /// <summary>
        /// Gets or sets the developer's Crocodoc API token.
        /// </summary>
        public static string ApiToken
        {
            get
            {
                if (String.IsNullOrEmpty(_apiToken))
                {
                    throw new ArgumentException("ApiToken is missing. It is required to interact with the Crocodoc API");
                }
                return _apiToken;
            }
            set
            {
                _apiToken = value;
            }
        }

        /// <summary>
        /// Gets or sets the default base path on the server where the API lives.
        /// </summary>
        public static string BasePath
        {
            get
            {
                return _basePath;
            }
            set
            {
                _basePath = value;
            }
        }

        /// <summary>
        /// Gets or sets the default host.
        /// </summary>
        public static string Host
        {
            get
            {
                return _host;
            }
            set
            {
                _host = value;
            }
        }

        /// <summary>
        /// Gets or sets an API path relative to the base API path.
        /// </summary>
        public static string Path
        {
            get
            {
                return _path;
            }
            set
            {
                _path = value;
            }
        }

        /// <summary>
        /// Gets or sets the default protocol (Crocodoc uses HTTPS).
        /// </summary>
        public static string Protocol
        {
            get
            {
                return _protocol;
            }
            set
            {
                _protocol = value;
            }
        }

        /// <summary>
        /// Gets or sets the WebProxy instance if one is needed (leave null if not needed).
        /// </summary>
        public static WebProxy Proxy
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the UriBuilder, complete and ready to be passed to the request.
        /// </summary>
        /// <param name="path">The path to be appended.</param>
        /// <param name="method">The method to be appended, ie "create".</param>
        /// <returns>The UriBuilder ready to be passed to the Request.</returns>
        private static UriBuilder GetUriBuilder(string path, string method)
        {
            UriBuilder uriBuilder = new UriBuilder();
            uriBuilder.Scheme = Protocol;
            uriBuilder.Host = Host;
            uriBuilder.Path = BasePath + Path + path + method;

            return uriBuilder;
        }

        /// <summary>
        /// Makes a GET request to the Crocodoc API and returns the response data as a byte array.
        /// </summary>
        /// <param name="path">The path to be appended.</param>
        /// <param name="method">The method to be appened, ie "create".</param>
        /// <param name="parameters">The parameters to pass to the request.</param>
        /// <returns>The byte array data returned from the request.</returns>
        protected static byte[] GetData(string path, string method, NameValueCollection parameters)
        {
            UriBuilder uriBuilder = GetUriBuilder(path, method);
            string query = "token=" + ApiToken;

            foreach (string key in parameters.AllKeys)
            {
                query += String.Format("&{0}={1}", key, parameters[key]);
            }
            uriBuilder.Query = query;

            WebClient client = new WebClient();
            if (Proxy != null)
            {
                client.Proxy = Proxy;
            }

            try
            {
                return client.DownloadData(uriBuilder.Uri.ToString().Replace("??", "?"));
            }
            catch (Exception ex)
            {
                if (ex is WebException)
                {
                    throw new CrocodocException((WebException)ex);
                }
                else
                {
                    throw ex;
                }
            }
            finally
            {
                client.Dispose();
            }
        }

        /// <summary>
        /// Makes a GET request to the Crocodoc REST API.
        /// </summary>
        /// <typeparam name="T">The Type generic to return from this request.</typeparam>
        /// <param name="path">The path to be appended.</param>
        /// <param name="method">The method to be appened, ie "create".</param>
        /// <param name="parameters">The parameters to pass to the request.</param>
        /// <returns>The Type as specified by T.</returns>
        protected static T GetRequest<T>(string path, string method, NameValueCollection parameters)
        {
            byte[] result = GetData(path, method, parameters);

            DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T));

            using (MemoryStream stream = new MemoryStream(result))
            {
                return (T)serializer.ReadObject(stream);
            }
        }

        /// <summary>
        /// Posts a file using multipart post to Crocodoc REST API.
        /// </summary>
        /// <typeparam name="T">The Type generic to return from this request.</typeparam>
        /// <param name="path">The path to be appended.</param>
        /// <param name="method">The method to be appened, ie "create".</param>
        /// <param name="file">The file stream.</param>
        /// <param name="parameters">The parameters to pass to the request.</param>
        /// <returns>The Type as specified by T.</returns>
        /// <remarks>Thanks to "http://www.codeproject.com/Articles/72232/C-File-Upload-with-form-fields-cookies-and-headers"</remarks>
        protected static T PostFile<T>(string path, string method, Stream file, string fileName, NameValueCollection parameters)
        {
            UriBuilder uriBuilder = GetUriBuilder(path, method);
            if (parameters == null)
            {
                parameters = new NameValueCollection();
            }
            parameters.Add("token", ApiToken);

            HttpWebRequest client = (HttpWebRequest)HttpWebRequest.Create(uriBuilder.Uri);
            client.Method = "POST";
            if (Proxy != null)
            {
                client.Proxy = Proxy;
            }

            string boundary = "------" + DateTime.Now.Ticks.ToString("x", CultureInfo.InvariantCulture);
            client.ContentType = "multipart/form-data; boundary=" + boundary;

            StringBuilder sb = new StringBuilder();
            foreach (string key in parameters.AllKeys)
            {
                sb.AppendFormat("--{0}\r\n", boundary);
                sb.AppendFormat("Content-Disposition: form-data; name=\"{0}\";\r\n\r\n{1}\r\n", key, parameters[key]);
            }

            sb.AppendFormat("--{0}\r\n", boundary);
            sb.AppendFormat("Content-Disposition: form-data; name=\"file\"; filename=\"{0}\";\r\n", fileName);
            sb.AppendFormat("Content-Type: {0}\r\n\r\n", GetContentType(fileName));

            byte[] header = Encoding.UTF8.GetBytes(sb.ToString());
            byte[] footer = Encoding.UTF8.GetBytes("\r\n--" + boundary + "--\r\n");
            client.ContentLength = header.Length + file.Length + footer.Length;
            
            try
            {
                using (Stream stream = client.GetRequestStream())
                {
                    stream.Write(header, 0, header.Length);

                    byte[] buffer = new Byte[checked((uint)Math.Min(4096, (int)file.Length))];
                    int bytesRead;
                    while ((bytesRead = file.Read(buffer, 0, buffer.Length)) != 0)
                    {
                        stream.Write(buffer, 0, bytesRead);
                    }
                    stream.Write(footer, 0, footer.Length);

                    WebResponse response = client.GetResponse();
                    
                    DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T));
                    return (T)serializer.ReadObject(response.GetResponseStream());
                }
            }
            catch (WebException ex)
            {
                throw new CrocodocException(ex);
            }
        }

        /// <summary>
        /// Makes a post request to the Crocodoc REST API
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="path"></param>
        /// <param name="method"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        protected static T PostRequest<T>(string path, string method, NameValueCollection parameters)
        {
            UriBuilder uriBuilder = GetUriBuilder(path, method);

            if (parameters != null)
            {
                parameters.Add("token", ApiToken);
            }

            WebClient client = new WebClient();
            if (Proxy != null)
            {
                client.Proxy = Proxy;
            }

            byte[] result;
            try
            {
                result = client.UploadValues(uriBuilder.Uri, "POST", parameters);
            }
            catch (WebException ex)
            {
                throw new CrocodocException(ex);
            }
            finally
            {
                client.Dispose();
            }

            DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T));

            using (MemoryStream stream = new MemoryStream(result))
            {
                return (T)serializer.ReadObject(stream);
            }
        }

        /// <summary>
        /// Gets a content type given a specific filename.
        /// </summary>
        /// <param name="filename">The filename to check.</param>
        /// <returns>The mime type.</returns>
        private static string GetContentType(string filename)
        {
            string key = System.IO.Path.GetExtension(filename);
            if (key.Length > 0)
            {
                key = key.Remove(0, 1);
                if (MIMETypesDictionary.ContainsKey(key))
                {
                    return MIMETypesDictionary[key];
                }
            }
            return "application/octet-stream";
        }

        /// <summary>
        /// Dictionary containing mime types based on file extensions.
        /// </summary>
        private static Dictionary<string, string> MIMETypesDictionary = new Dictionary<string, string>
        {
            {"ai", "application/postscript"},
            {"aif", "audio/x-aiff"},
            {"aifc", "audio/x-aiff"},
            {"aiff", "audio/x-aiff"},
            {"asc", "text/plain"},
            {"atom", "application/atom+xml"},
            {"au", "audio/basic"},
            {"avi", "video/x-msvideo"},
            {"bcpio", "application/x-bcpio"},
            {"bin", "application/octet-stream"},
            {"bmp", "image/bmp"},
            {"cdf", "application/x-netcdf"},
            {"cgm", "image/cgm"},
            {"class", "application/octet-stream"},
            {"cpio", "application/x-cpio"},
            {"cpt", "application/mac-compactpro"},
            {"csh", "application/x-csh"},
            {"css", "text/css"},
            {"dcr", "application/x-director"},
            {"dif", "video/x-dv"},
            {"dir", "application/x-director"},
            {"djv", "image/vnd.djvu"},
            {"djvu", "image/vnd.djvu"},
            {"dll", "application/octet-stream"},
            {"dmg", "application/octet-stream"},
            {"dms", "application/octet-stream"},
            {"doc", "application/msword"},
            {"docx","application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
            {"dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template"},
            {"docm","application/vnd.ms-word.document.macroEnabled.12"},
            {"dotm","application/vnd.ms-word.template.macroEnabled.12"},
            {"dtd", "application/xml-dtd"},
            {"dv", "video/x-dv"},
            {"dvi", "application/x-dvi"},
            {"dxr", "application/x-director"},
            {"eps", "application/postscript"},
            {"etx", "text/x-setext"},
            {"exe", "application/octet-stream"},
            {"ez", "application/andrew-inset"},
            {"gif", "image/gif"},
            {"gram", "application/srgs"},
            {"grxml", "application/srgs+xml"},
            {"gtar", "application/x-gtar"},
            {"hdf", "application/x-hdf"},
            {"hqx", "application/mac-binhex40"},
            {"htm", "text/html"},
            {"html", "text/html"},
            {"ice", "x-conference/x-cooltalk"},
            {"ico", "image/x-icon"},
            {"ics", "text/calendar"},
            {"ief", "image/ief"},
            {"ifb", "text/calendar"},
            {"iges", "model/iges"},
            {"igs", "model/iges"},
            {"jnlp", "application/x-java-jnlp-file"},
            {"jp2", "image/jp2"},
            {"jpe", "image/jpeg"},
            {"jpeg", "image/jpeg"},
            {"jpg", "image/jpeg"},
            {"js", "application/x-javascript"},
            {"kar", "audio/midi"},
            {"latex", "application/x-latex"},
            {"lha", "application/octet-stream"},
            {"lzh", "application/octet-stream"},
            {"m3u", "audio/x-mpegurl"},
            {"m4a", "audio/mp4a-latm"},
            {"m4b", "audio/mp4a-latm"},
            {"m4p", "audio/mp4a-latm"},
            {"m4u", "video/vnd.mpegurl"},
            {"m4v", "video/x-m4v"},
            {"mac", "image/x-macpaint"},
            {"man", "application/x-troff-man"},
            {"mathml", "application/mathml+xml"},
            {"me", "application/x-troff-me"},
            {"mesh", "model/mesh"},
            {"mid", "audio/midi"},
            {"midi", "audio/midi"},
            {"mif", "application/vnd.mif"},
            {"mov", "video/quicktime"},
            {"movie", "video/x-sgi-movie"},
            {"mp2", "audio/mpeg"},
            {"mp3", "audio/mpeg"},
            {"mp4", "video/mp4"},
            {"mpe", "video/mpeg"},
            {"mpeg", "video/mpeg"},
            {"mpg", "video/mpeg"},
            {"mpga", "audio/mpeg"},
            {"ms", "application/x-troff-ms"},
            {"msh", "model/mesh"},
            {"mxu", "video/vnd.mpegurl"},
            {"nc", "application/x-netcdf"},
            {"oda", "application/oda"},
            {"ogg", "application/ogg"},
            {"pbm", "image/x-portable-bitmap"},
            {"pct", "image/pict"},
            {"pdb", "chemical/x-pdb"},
            {"pdf", "application/pdf"},
            {"pgm", "image/x-portable-graymap"},
            {"pgn", "application/x-chess-pgn"},
            {"pic", "image/pict"},
            {"pict", "image/pict"},
            {"png", "image/png"}, 
            {"pnm", "image/x-portable-anymap"},
            {"pnt", "image/x-macpaint"},
            {"pntg", "image/x-macpaint"},
            {"ppm", "image/x-portable-pixmap"},
            {"ppt", "application/vnd.ms-powerpoint"},
            {"pptx","application/vnd.openxmlformats-officedocument.presentationml.presentation"},
            {"potx","application/vnd.openxmlformats-officedocument.presentationml.template"},
            {"ppsx","application/vnd.openxmlformats-officedocument.presentationml.slideshow"},
            {"ppam","application/vnd.ms-powerpoint.addin.macroEnabled.12"},
            {"pptm","application/vnd.ms-powerpoint.presentation.macroEnabled.12"},
            {"potm","application/vnd.ms-powerpoint.template.macroEnabled.12"},
            {"ppsm","application/vnd.ms-powerpoint.slideshow.macroEnabled.12"},
            {"ps", "application/postscript"},
            {"qt", "video/quicktime"},
            {"qti", "image/x-quicktime"},
            {"qtif", "image/x-quicktime"},
            {"ra", "audio/x-pn-realaudio"},
            {"ram", "audio/x-pn-realaudio"},
            {"ras", "image/x-cmu-raster"},
            {"rdf", "application/rdf+xml"},
            {"rgb", "image/x-rgb"},
            {"rm", "application/vnd.rn-realmedia"},
            {"roff", "application/x-troff"},
            {"rtf", "text/rtf"},
            {"rtx", "text/richtext"},
            {"sgm", "text/sgml"},
            {"sgml", "text/sgml"},
            {"sh", "application/x-sh"},
            {"shar", "application/x-shar"},
            {"silo", "model/mesh"},
            {"sit", "application/x-stuffit"},
            {"skd", "application/x-koan"},
            {"skm", "application/x-koan"},
            {"skp", "application/x-koan"},
            {"skt", "application/x-koan"},
            {"smi", "application/smil"},
            {"smil", "application/smil"},
            {"snd", "audio/basic"},
            {"so", "application/octet-stream"},
            {"spl", "application/x-futuresplash"},
            {"src", "application/x-wais-source"},
            {"sv4cpio", "application/x-sv4cpio"},
            {"sv4crc", "application/x-sv4crc"},
            {"svg", "image/svg+xml"},
            {"swf", "application/x-shockwave-flash"},
            {"t", "application/x-troff"},
            {"tar", "application/x-tar"},
            {"tcl", "application/x-tcl"},
            {"tex", "application/x-tex"},
            {"texi", "application/x-texinfo"},
            {"texinfo", "application/x-texinfo"},
            {"tif", "image/tiff"},
            {"tiff", "image/tiff"},
            {"tr", "application/x-troff"},
            {"tsv", "text/tab-separated-values"},
            {"txt", "text/plain"},
            {"ustar", "application/x-ustar"},
            {"vcd", "application/x-cdlink"},
            {"vrml", "model/vrml"},
            {"vxml", "application/voicexml+xml"},
            {"wav", "audio/x-wav"},
            {"wbmp", "image/vnd.wap.wbmp"},
            {"wbmxl", "application/vnd.wap.wbxml"},
            {"wml", "text/vnd.wap.wml"},
            {"wmlc", "application/vnd.wap.wmlc"},
            {"wmls", "text/vnd.wap.wmlscript"},
            {"wmlsc", "application/vnd.wap.wmlscriptc"},
            {"wrl", "model/vrml"},
            {"xbm", "image/x-xbitmap"},
            {"xht", "application/xhtml+xml"},
            {"xhtml", "application/xhtml+xml"},
            {"xls", "application/vnd.ms-excel"},                        
            {"xml", "application/xml"},
            {"xpm", "image/x-xpixmap"},
            {"xsl", "application/xml"},
            {"xlsx","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
            {"xltx","application/vnd.openxmlformats-officedocument.spreadsheetml.template"},
            {"xlsm","application/vnd.ms-excel.sheet.macroEnabled.12"},
            {"xltm","application/vnd.ms-excel.template.macroEnabled.12"},
            {"xlam","application/vnd.ms-excel.addin.macroEnabled.12"},
            {"xlsb","application/vnd.ms-excel.sheet.binary.macroEnabled.12"},
            {"xslt", "application/xslt+xml"},
            {"xul", "application/vnd.mozilla.xul+xml"},
            {"xwd", "image/x-xwindowdump"},
            {"xyz", "chemical/x-xyz"},
            {"zip", "application/zip"}
        };
    }
}